import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
描述:JVM
 * 1.JVM内存区域
 *      1.本地内存区（元空间（方法区））：也叫堆外内存，有元空间，存储类的信息，常量，静态变量等，共享区域，无需GC
 *      2.堆：存储对象实例，数组等，共享区域，需要GC
 *      3.虚拟机栈：方法执行时的内存模型，方法执行创建栈帧，执行完出栈，保存执行方法时候的局部变量，操作数栈，动态链接和返回值地址信息，线程私有，无需GC
 *      4.程序计数器：当前线程执行字节码指令的行号指示器，行号可以知道多线程执行挂起的位置，线程私有，无需GC
 *      5.本地方法栈：虚拟机执行本地方法时用到，线程私有，无需GC
 * 2.垃圾回收（GC）
 *      1.如何判断对象是垃圾（如何识别垃圾）
 *          1.没有被引用
 *          2.引用计数法：无法解决循环引用问题,占据额外空间
 *          3.可达性分析：标记可达对象，清楚不可达对象 GC ROOT 遍历扫描，寻找可达对象
 *      2.对象被GCROOT（被标记了）就一定会被回收吗
 *          1.不一定，对象有finalize方法第一次不会被回收，第二次就会忽略并回收
 *      3.哪些对象可以GCROOT
 *          1.虚拟机栈中引用的对象（栈上引用）
 *          2.方法区中类静态属性引用的对象（静态变量引用）
 *          3.方法区中常量引用的对象（常量引用）
 *          4.本地方法栈中JNI（native修饰的方法）引用的对象（native引用）
 *      4.垃圾回收主要算法
 *          1.复制算法：主要发生在年轻代，suviror0区到1区复制，解决内存碎片，增加了内存开销
 *          2.标记-清除：垃圾标记后，直接清除，容易产生内存碎片
 *          3.标记-整理：垃圾标记后，先清除，整理，解决内存碎片，增加cpu开销，时间开销
 *          5.分代收集算法：年轻代用复制算法，young gc，老年代用标记-整理 old gc。
 *              垃圾先进入eden区，满了youngc，对象回收，到suviror0区，对象年龄加1，再满了就到surviror1区，对象年龄再加1，
 *              年龄到达设定阈值，晋升老年代
 *      5.对象何时晋升老年代
 *          1.年轻代中对象年龄到达一定阈值
 *          2.对象较大，年轻代复制消耗较大，存储的大小
 *          3.suviror区相同年龄的对象大小之和大于空间的一半，则年龄大于该年龄的对象也会去老年代
 *      6.safe point：垃圾回收的时候需要进行stw
 *          位置：循环的末尾，方法返回前，调用方法的call之后，抛出异常的位置
 *      7.垃圾回收器：
 *          新生代：Serial，Parnew，ParallelScavenge
 *          老年代：CMS，Serial Old，Parnew Old
 *          全带：G1
 *          Srial：单线程
 *          Serial Old：单线程
 *          Parnew：多线程搜集，与CMS配合，关注减少stw
 *          ParallelScavenge：多线程，关注吞吐量
 *          Parallel Old:多线程，标记整理，吞吐量优先
 *          CMS：以实现最短STW为目标，提升服务响应速度，标记清楚算法
 *              1.初始标记：STW,找到GC ROOT
 *              2.并发标记：通过GC ROOT遍历并发标记
 *              3.重新标记：STW，标记初始标记的STW新生成的垃圾
 *              4.并发清除：
 *              缺点：对CPU资源敏感，无法处理浮动垃圾，标记清除算法，内存碎片
 *          G1：标记整理，不产生内存碎片，可预测的STW模型
 *              G1各代的地址不连续，每一代都使用了N个不连续的大小相同的region，每个region占用一块连续地址
 *              有存储大对象的Region，存储大小大于等于region一半的对象
 *              维护了一个region垃圾堆价值大小的值，优先回收价值大的，避免了整个老年代的回收，减少STW时间
 *              1.初始标记
 *              2.并发标记
 *              3.最终标记
 *              4.筛选回收：根据region的回收价值进行筛选排序，指定用户期望的回收计划
 *      8.什么时候会触发full gc
 *          1.老年代空间不足，可能是由于一个大对象突然进去，导致年轻代存不下，直接放入老年代，老年代也存不下，引发full gc。或者单纯空间满了引发的java heap space
 *          2.调用system.gc这个方法的时候
 *          3.方法区，也叫元空间的空间不足，里面存储的类信息，java反射的信息，常量等等如果满了，也会引发full gc
 * 3.类加载过程：
 *      1.加载
 *          1.通过类的全类名获取类的二进制字节流
 *          2.将字节流所代表的静态存储结构转换为方法区运行时的数据结构
 *          3.在类中生成代表该类的class对象，作为方法区这些数据的访问入口
 *      2.连接
 *          1.验证：验证类是否合法，保证能被jvm运行。文件格式，元数据，字节码，符号引用
 *          2.准备：为类变量(static)分配内存并设置类变量初始值，初始值一般是0，null,false等，除非final修饰，才会直接赋值
 *          3.解析：将常量池内的符号引用替换为直接引用，得到类或字段，方法在内存中的指针或者偏移量
 *      3.初始化
 *          1.执行类构造器的<clinit>方法（字节码中有）
 *          2.什么情况需要初始化：
 *              1.new对象，或者调用到类的静态变量，静态方法
 *              2.反射对象
 *              3.初始化类，父类还未初始化，先初始化父类
 *              4.main方法
 * 4.双亲委派机制
 *      1.定义：当前类加载器加载类的时候，会委托给其双亲（其实也是类加载器）先进行加载，双亲类加载器在加载的同时还会去寻找自身的双亲，直到双亲为null
 *      2.顺序：系统类加载器-拓展类加载器-启动类加载
 *      3.作用：
 *          1.保证安全性：防止重复加载一个.class
 *          2.保证唯一性：预防代码被篡改，比如用户自定义的一个Object，跟原来的产生了冲突
 * 5.JVM调优：
 *      1.JVM调优的时机
 *          1.heap内存上涨，老年代空间满了
 *          2.full gc次数频繁
 *          3.GC停顿时间过长（超过1s）
 *          4.应用出现OOM的异常
 *          5.系统吞吐量和响应性能不高或者下降
 *      2.JVM调优的目标
 *          1.吞吐量，延迟，内存三个是类似CAP的关系，只能选择其中两个，不可兼得。但是当今硬件技术发展到现在，内存已经不是成本非常高的属性，可以牺牲一定内存
 *            保证高吞吐量和低延迟
 *      3.JVM调优的步骤
 *          1.分析系统的情况，分析gc或者dump文件的内存对象占用情况，判断是否需要优化，确定系统瓶颈
 *          2.确定调优的量化目标，吞吐量，延迟，内存的指标
 *          3.选定调优参数，不断优化，确定最后的最优参数
 *      4.常用调优策略
 *          1.选择合适的垃圾回收器
 *              1.单核：serial
 *              2.多核，关注吞吐量：选择Parallel Scavenge,Parallel Old
 *              3.多核，关注用户停顿时间：jdk1.6 ， jdk1.7 选择CMS
 *              4.多核，关注用户停顿时间：jdk1.8以上并且系统的内存高于6g，选择g1
 *          2.调整内存大小
 *              1.问题：垃圾收集频率非常频繁
 *              2.原因：内存太小，就需要频繁的进行垃圾回收释放内存空间，所以增加堆内存大小比较好
 *              3.注意：如果垃圾收集次数非常频繁但是每次回收的对象很少，那可能是因为内存泄漏导致的
 *              4.参数：-Xms2g -Xmx2g
 *          3.设置符合预期的停顿时间
 *              1.问题：程序间歇性卡顿
 *              2.原因：如果没有确切的停顿时间的设定，垃圾收集器以吞吐量为主，则stw时间可能就会变长，系统不稳定
 *              3.注意：不要设置不切实际的停顿时间，越短也就意味着垃圾回收次数要更多，因为一次回收的比较少
 *              4.参数：-XX:MaxGCPauseMillis
 *          4.调整内存区域大小比率
 *              1.问题：某一个区域的GC非常频繁，其它的地方都正常
 *              2.原因：如果某个区域空间不足，导致需要频繁GC，而不能调整内存大小，就调整内存比率
 *              3.注意：也许并不是空间不足，而是内存泄漏导致内存无法回收，从而GC频繁
 *              4.参数：-XX:SurvivorRatio=6，S区和Eden区占新生代比率为1:6,两个S区2:6；-XX:NewRatio=4，新生代:老年代 = 1:4 即老年代占整个堆的4/5；默认值=2
 *          5.调整对象升老年代的年龄
 *              1.问题：老年代频繁GC，每次回收的对象很多
 *              2.原因：如果升代年龄小，新生代很快就会进入老年代，导致老年代对象变多，而这些对象其实在随后的很短时间内就可以回收，可以提高升代年龄，让对象不那么容易进入老年代
 *              3.注意：增加了年龄，那么对象在新生代中就会呆的很长，导致新生代的GC频率变高，并且频繁复制这些对象，新生代的GC时间可能会变长
 *              4.参数：-XX:InitialTenuringThreshol=7 进入老年代最小的GC年龄,年轻代对象转换为老年代对象最小年龄值，默认值7
 *          6.调整大对象的标准
 *              1.问题：老年代频繁GC，每次回收的对象很多，而且单个对象体积较大
 *              2.原因：如果大对象直接分配到老年代，导致老年代空间满造成频繁GC，可设置对象直接进入老年代的标准
 *              3.注意：这些大对象进入新生代之后可能会使新生代的GC频率和时间增加
 *              4.参数：-XX:PretenureSizeThreshold=1000000 新生代可容纳的最大对象,大于则直接会分配到老年代，0代表没有限制
 *          7.调整GC的触发时机
 *              1.问题：CMS，G1 经常 Full GC，程序卡顿严重
 *              2.原因：G1和CMS的部分GC阶段是并发执行的，业务线程和垃圾回收线程一起工作，也就说明在GC的过程中还是会产生新的对象，所以GC的时候需要预留一部分的空间
 *                容纳新的对象，如果这时候内存空间不足以容纳新产生的对象，那么JVM就会停止并发收集开始STW来保证垃圾回收的正常进行。这时候可以调整GC的触发时机（比如
 *                老年代占用60%的时候就触发GC），这样可以预留足够的空间让业务线程创建对象有足够的内存
 *              3.注意：提早触发GC会增加老年代GC的频率
 *              4.参数：-XX:G1MixedGCLiveThresholdPercent=65 G1混合垃圾回收周期中要包括的旧区域设置占用率阈值。默认占用率为 65%
 *          8.调整JVM本地内存大小
 *              1.问题：GC的次数、时间和回收对象都正常，堆空间充足，但是报OOM
 *              2.原因：JVM除了堆内存外，还有一块堆外内存，也叫本地内存，但是堆外内存并不会主从触发GC，只有在堆内存区域触发的时候顺带会把本地内存回收了，如果分配不足
 *                就会报OOM
 *              3.注意：本地内存异常除了上面的现象外，可能日志会报：OutOfMemoryError：Direct buffer memory
 *              4.参数：XX:MaxDirectMemorySize
 *       5.JVM调优实例s
 *          1.网站流量暴增后，网站页面响应很慢
 *              1.问题推测：可能是因为垃圾收集导致的业务线程停顿
 *              2.定位：通过jstat -gc 查看垃圾回收频率，非常高，GC占用的时间比较长，导致业务停顿，从而网页响应慢
 *              3.解决：网页访问量太大，对象创建快，从而频繁GC，问题就是JVM内存设置的太小了，从原来的4G调到了16G
 *              4.问题：调整后发现请求是比较快了点，但是又出现另一个问题，会不定期的间歇性卡顿，单次卡顿时间很长
 *              5.定位：推测是加大了内存之后，单词full gc时间变长导致的，有高达十几秒的
 *              6.解决：用的是jdk1.8，默认是parallel scavange 和 parallel old的垃圾回收器，这个垃圾回收器注重的是吞吐量，垃圾的标记和收集阶段都是STW，
 *                所以内存加大了之后，需要进行垃圾回收的时间就边长了，可以改成cms或者g1，因为系统的内存可以达到6g以上，g1比较适合大内存的垃圾回收，然后设置了
 *                一个预期的停顿时间，就ok了
 *          2.经常出现CPU100%问题定位
 *              1.问题：CPU高应该是某个程序长期占用CPU资源，要么是锁资源占用，要么是频繁gc造成的
 *              2.查找：
 *                  1.所以先需要找出那个进行占用CPU高：top查看cpu高的进程
 *                  2.然后根据找到对应进行里哪个线程占用CPU高：top -Hp 进程id，找到线程id转换为16进制的id
 *                  3.jstack打印线程堆栈信息：jstack -l 进程id > log.log
 *          3.内存飙高问题定位
 *              1.问题：如果内存飙高是发生在java进程上，一般是因为创建了大对象导致的，持续飙高说明大对象得不到回收，或者内存泄漏无法回收
 *              2.查找：
 *                  1.先观察垃圾回收的情况 jstat -gc pid 1000，每隔一秒打印gc信息
 *                  2.jmap -histo pid | head -20 ，打印出占用内存最大的前20个对象
 *                  3.dump出堆内存快照文件，进行离线分析，找到大内存对象的代码，看是否能优化
 *          4.内存泄漏定位
 *              1.因为OOM导致系统挂掉：启动命令设置出现oom就输出dump文件的指令
 *              2.OOM系统没挂掉：jmap -dump:format=b  然后文件用jvisualvm解析，查看到比较占用内存的类，找到GC ROOT，查看引用,定位代码位置
 *          5.以前做审批系统的时候，遇到的jvm问题和优化：
 *              1.日志中报oom：java heap space，堆空间满了，加大了空间
 *              2.full gc很频繁，这个频繁在有一段时间二三十分钟就会发生一次，一次2-3s，通过jvisualvm和printGCdetail一起结合看的，初步断定的原因是
 *                当时是觉得因为上传文件的人太多了，还有执行sql的时候查询出的数据太多，就加大了堆空间
 *              3.有报过一次的stackoverflow，原因是代码中写了循环没跳出去，条件判断不对，把刚上线的代码看了一遍
 * 6.内存泄漏
 *      1.一次性申请的对象太多，查询的数据太多，比如数据库一次性查询千万级的数据，放到list中，需要做分页查询
 *      2.内存资源耗尽，没有释放，比如开启io流，没有关闭io流，比如开启jdbcConnection没有关闭，可用在finnally中关闭或者使用池化思想
 *      3.本身内存资源不够，jmap -heap 查看内存分布大小，需要调整堆内存大小
 *      4.代码编写不但
 *          1.static字段引起的内存泄漏，static修饰的变量方法是跟类的生命周期是一样的，会一直存在
 *          2.未关闭的资源导致的内存泄漏，
 *          3.引用了外部类的内部类，非静态内部类的初始化总是需要引用到外部类的实例的，会导致外部类无法被回收
 *          4.finalize方法导致的内存泄漏，如果重写finalize方法，则第一次不会被垃圾回收，但是代码要是有问题，就会导致内存泄漏
 *          5.ThreadLocal导致的内存泄漏
 *          6.重写hashcode和equals方法不当导致的内存泄漏
 * 7.JAVA对象结构
 *      1.对象头
 *          1.Mark Word：主要标记对象的线程锁状态，还有GC的年龄，对象的HashCode
 *              1.用二进制字节表示：01表示正常或者偏向锁，00表示轻量级锁，10表示重量级锁，11表示GC
 *              2.锁升级：
 *                  1.初期锁对象刚创建时，还没有任何线程来竞争，对象的Mark Word是下图的第一种情形，这偏向锁标识位是0，锁状态01，说明该对象处于无锁状态（无线程竞争它）
 *                  2.当有一个线程来竞争锁时，先用偏向锁，表示锁对象偏爱这个线程，这个线程要执行这个锁关联的任何代码，不需要再做任何检查和切换，
 *                    这种竞争不激烈的情况下，效率非常高。这时Mark Word会记录自己偏爱的线程的ID，把该线程当做自己的熟人
 *                  3.当有两个线程开始竞争这个锁对象，情况发生变化了，不再是偏向（独占）锁了，锁会升级为轻量级锁，两个线程公平竞争，哪个线程先占有锁对象并执行代码，
 *                    锁对象的Mark Word就执行哪个线程的栈帧中的锁记录
 *                      1.在另一个线程进来的时候，会去查看markword中记录的线程Id，如果那个线程是挂了，则对象是可以变成无锁状态的，然后重新偏向
 *                  4.如果竞争的这个锁对象的线程更多，导致了更多的切换和等待，JVM会把该锁对象的锁升级为重量级锁，这个就叫做同步锁，这个锁对象Mark Word再次发生变化，
 *                    会指向一个监视器对象（moniter对象），这个监视器对象用集合的形式，来登记和管理排队的线程
 *          2.Klass Word：存储对应到元空间中类对象信息的指针
 *          3.数组长度：如果该对象是数组，会保存数组的长度
 *      2.对象体：对象的属性值
 *      3.对其填充：对齐字节，可能用来减少内存碎片空间
 * 8.对象一定分布在堆上吗
 *      1.不一定，可能是栈上分配
 *      2.java发展到现在，编译器已经很成熟了，java的jit编译器，会对变量进行逃逸分析
 *      3.逃逸分析：是比如在一个方法内定义了一个变量，然后这个变量被其它方法引用了，就发生了逃逸
 *      4.jit编译器进行逃逸分析，如果发现没有逃逸的变量，就会将这个变量的对象栈上分配，栈上分配是有内存限制的，为1M
 *
 * @author
 * @created 2022/1/11 18:49
 */
public class JVM {

    public static List<int[]> list = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        while (true) {
            int[] ints = new int[1024 * 10 * 10];
            list.add(ints);
            TimeUnit.SECONDS.sleep(1);
        }
    }
}
